#!/usr/bin/env python3
import os

import common
from shell_helpers import LF

class Main(common.BuildCliFunction):
    def __init__(self):
        super().__init__(
            description='''\
Build the baremetal examples with crosstool-NG.
''',
            supported_archs=common.consts['crosstool_ng_supported_archs']
        )

    def build(self):
        build_dir = self.get_build_dir()
        bootloader_obj = os.path.join(self.env['baremetal_build_lib_dir'], 'bootloader{}'.format(self.env['obj_ext']))
        common_basename_noext = 'common'
        common_src = os.path.join(self.env['root_dir'], common_basename_noext + self.env['c_ext'])
        common_obj = os.path.join(self.env['baremetal_build_lib_dir'], common_basename_noext + self.env['obj_ext'])
        syscalls_basename_noext = 'syscalls'
        syscalls_src = os.path.join(self.env['baremetal_source_lib_dir'], syscalls_basename_noext + self.env['c_ext'])
        syscalls_obj = os.path.join(self.env['baremetal_build_lib_dir'], syscalls_basename_noext + self.env['obj_ext'])
        common_objs = [common_obj, syscalls_obj]
        cflags = [
            '-I', self.env['baremetal_source_lib_dir'], LF,
            '-I', self.env['root_dir'], LF,
            '-O0', LF,
            '-ggdb3', LF,
            '-mcpu={}'.format(self.env['mcpu']), LF,
            '-nostartfiles', LF,
        ]
        if self.env['prebuilt']:
            gcc = 'arm-none-eabi-gcc'
        else:
            os.environ['PATH'] = self.env['crosstool_ng_bin_dir'] + os.environ['PATH']
            gcc = self.get_toolchain_tool('gcc', allowed_toolchains=['crosstool-ng'])
        if self.env['emulator'] == 'gem5':
            if self.env['machine'] == 'VExpress_GEM5_V1':
                entry_address = 0x80000000
                uart_address = 0x1c090000
            elif self.env['machine'] == 'RealViewPBX':
                entry_address = 0x10000
                uart_address = 0x10009000
            else:
                raise Exception('unknown machine: ' + self.env['machine'])
            cflags.extend(['-D', 'GEM5'.format(uart_address), LF])
        else:
            entry_address = 0x40000000
            uart_address = 0x09000000
        os.makedirs(build_dir, exist_ok=True)
        os.makedirs(self.env['baremetal_build_lib_dir'], exist_ok=True)
        src = os.path.join(self.env['baremetal_source_lib_dir'], '{}{}'.format(self.env['arch'], self.env['asm_ext']))
        if self.need_rebuild([src], bootloader_obj):
            self.sh.run_cmd(
                [gcc,  LF] +
                cflags +
                [
                    '-c', LF,
                    '-o', bootloader_obj, LF,
                    src, LF,
                ]
            )
        for src, obj in [
            (common_src, common_obj),
            (syscalls_src, syscalls_obj),
        ]:
            if self.need_rebuild([src], obj):
                self.sh.run_cmd(
                    [gcc,  LF] +
                    cflags +
                    [
                        '-c', LF,
                        '-D', 'UART0_ADDR={:#x}'.format(uart_address), LF,
                        '-o', obj, LF,
                        src, LF,
                    ]
                )
        self._build_dir(
            '',
            gcc=gcc,
            cflags=cflags,
            entry_address=entry_address,
            bootloader_obj=bootloader_obj,
            common_objs=common_objs,
        )
        self._build_dir(
            'interactive',
            gcc=gcc,
            cflags=cflags,
            entry_address=entry_address,
            bootloader_obj=bootloader_obj,
            common_objs=common_objs,
        )
        if os.path.isdir(os.path.join(self.env['baremetal_source_arch_dir'])):
            self._build_dir(
                self.env['baremetal_source_arch_subpath'],
                gcc=gcc,
                cflags=cflags,
                entry_address=entry_address,
                bootloader_obj=bootloader_obj,
                common_objs=common_objs,
            )
        arch_dir = os.path.join('arch', self.env['arch'], 'no_bootloader')
        if os.path.isdir(os.path.join(self.env['baremetal_source_dir'], arch_dir)):
            self._build_dir(
                arch_dir,
                gcc=gcc,
                cflags=cflags,
                entry_address=entry_address,
                bootloader_obj=bootloader_obj,
                common_objs=common_objs,
                bootloader=False,
            )

    def get_build_dir(self):
        return self.env['baremetal_build_dir']

    def _build_dir(
            self,
            subpath,
            gcc,
            cflags,
            entry_address,
            bootloader_obj,
            common_objs,
            bootloader=True
        ):
        '''
        Build all .c and .S files in a given subpath of the baremetal source
        directory non recursively.

        Place outputs on the same subpath or the output directory.
        '''
        in_dir = os.path.join(self.env['baremetal_source_dir'], subpath)
        out_dir = os.path.join(self.env['baremetal_build_dir'], subpath)
        os.makedirs(out_dir, exist_ok=True)
        common_objs = common_objs.copy()
        if bootloader:
            common_objs.append(bootloader_obj)
        for in_basename in os.listdir(in_dir):
            in_path = os.path.join(in_dir, in_basename)
            if os.path.isfile(in_path) and os.path.splitext(in_basename)[1] in (self.env['c_ext'], self.env['asm_ext']):
                in_name = os.path.splitext(in_basename)[0]
                main_obj = os.path.join(self.env['baremetal_build_dir'], subpath, '{}{}'.format(in_name, self.env['obj_ext']))
                src = os.path.join(self.env['baremetal_source_dir'], in_path)
                if self.need_rebuild([src], main_obj):
                    self.sh.run_cmd(
                        [gcc,  LF] +
                        cflags +
                        [
                            '-c', LF,
                            '-o', main_obj, LF,
                            src, LF,
                        ]
                    )
                objs = common_objs + [main_obj]
                out = os.path.join(self.env['baremetal_build_dir'], subpath, in_name + self.env['baremetal_build_ext'])
                link_script = os.path.join(self.env['baremetal_source_dir'], 'link.ld')
                if self.need_rebuild(objs + [link_script], out):
                    self.sh.run_cmd(
                        [gcc,  LF] +
                        cflags +
                        [
                            '-Wl,--section-start=.text={:#x}'.format(entry_address), LF,
                            '-o', out, LF,
                            '-T', link_script, LF,
                        ] +
                        self.sh.add_newlines(objs)
                    )

if __name__ == '__main__':
    Main().cli()
